#include "config.h"
+#include <math.h>
#include "gtkiconhelperprivate.h"
G_DEFINE_TYPE (GtkIconHelper, _gtk_icon_helper, G_TYPE_OBJECT)
GdkWindow *window;
GdkPixbuf *orig_pixbuf;
+ int orig_pixbuf_scale;
GdkPixbufAnimation *animation;
GIcon *gicon;
GtkIconSet *icon_set;
gchar *icon_name;
gchar *stock_id;
+ cairo_surface_t *orig_surface;
GtkIconSize icon_size;
gint pixel_size;
GdkPixbuf *rendered_pixbuf;
GtkStateFlags last_rendered_state;
+
+ cairo_surface_t *rendered_surface;
+ gint rendered_surface_width;
+ gint rendered_surface_height;
+ GtkStateFlags last_surface_state;
+ gint last_surface_scale;
};
void
g_clear_object (&self->priv->rendered_pixbuf);
g_clear_object (&self->priv->window);
+ if (self->priv->orig_surface)
+ {
+ cairo_surface_destroy (self->priv->orig_surface);
+ self->priv->orig_surface = NULL;
+ }
+
+ if (self->priv->rendered_surface)
+ {
+ cairo_surface_destroy (self->priv->rendered_surface);
+ self->priv->rendered_surface = NULL;
+ }
+
if (self->priv->icon_set != NULL)
{
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
self->priv->storage_type = GTK_IMAGE_EMPTY;
self->priv->icon_size = GTK_ICON_SIZE_INVALID;
self->priv->last_rendered_state = GTK_STATE_FLAG_NORMAL;
+ self->priv->last_surface_state = GTK_STATE_FLAG_NORMAL;
+ self->priv->last_surface_scale = 0;
}
void
self->priv->icon_size = GTK_ICON_SIZE_INVALID;
self->priv->pixel_size = -1;
self->priv->last_rendered_state = GTK_STATE_FLAG_NORMAL;
+ self->priv->orig_pixbuf_scale = 1;
}
static void
G_GNUC_END_IGNORE_DEPRECATIONS;
}
+static void
+get_surface_size (GtkIconHelper *self,
+ GtkStyleContext *context,
+ cairo_surface_t *surface,
+ int *width,
+ int *height)
+{
+ double x_scale, y_scale;
+
+ if (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE)
+ {
+ x_scale = y_scale = 1;
+
+#ifdef HAVE_CAIRO_SURFACE_SET_DEVICE_SCALE
+ cairo_surface_get_device_scale (surface, &x_scale, &y_scale);
+#endif
+
+ /* Assume any set scaling is icon scale */
+ *width =
+ ceil (cairo_image_surface_get_width (surface) / x_scale);
+ *height =
+ ceil (cairo_image_surface_get_height (surface) / y_scale);
+ }
+ else
+ ensure_icon_size (self, context, width, height);
+}
+
+static void
+ensure_pixbuf_from_surface (GtkIconHelper *self,
+ GtkStyleContext *context)
+{
+ cairo_surface_t *surface;
+ gint width, height;
+ cairo_t *cr;
+
+
+ if (!check_invalidate_pixbuf (self, context))
+ return;
+
+ if (self->priv->rendered_pixbuf)
+ return;
+
+ get_surface_size (self, context, self->priv->orig_surface, &width, &height);
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ width, height);
+
+ cr = cairo_create (surface);
+ cairo_set_source_surface (cr, self->priv->orig_surface, 0, 0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+
+ self->priv->rendered_pixbuf =
+ gdk_pixbuf_get_from_surface (surface, 0, 0, width, height);
+
+ cairo_surface_destroy (surface);
+}
+
static void
ensure_pixbuf_at_size (GtkIconHelper *self,
GtkStyleContext *context)
if (self->priv->rendered_pixbuf)
return;
- if (self->priv->pixel_size != -1 ||
- self->priv->icon_size != GTK_ICON_SIZE_INVALID)
+ if (self->priv->force_scale_pixbuf &&
+ (self->priv->pixel_size != -1 ||
+ self->priv->icon_size != GTK_ICON_SIZE_INVALID))
{
ensure_icon_size (self, context, &width, &height);
- if (width < gdk_pixbuf_get_width (self->priv->orig_pixbuf) ||
+ if (self->priv->orig_pixbuf_scale > 1 ||
+ /* These should divide the orig_pixbuf size by scale, but need not
+ due to the above scale > 1 check */
+ width < gdk_pixbuf_get_width (self->priv->orig_pixbuf) ||
height < gdk_pixbuf_get_height (self->priv->orig_pixbuf))
- self->priv->rendered_pixbuf =
- gdk_pixbuf_scale_simple (self->priv->orig_pixbuf,
- width, height,
- GDK_INTERP_BILINEAR);
+ {
+ width = MIN (width, gdk_pixbuf_get_width (self->priv->orig_pixbuf) / self->priv->orig_pixbuf_scale);
+ height = MIN (height, gdk_pixbuf_get_height (self->priv->orig_pixbuf) / self->priv->orig_pixbuf_scale);
+ self->priv->rendered_pixbuf =
+ gdk_pixbuf_scale_simple (self->priv->orig_pixbuf,
+ width, height,
+ GDK_INTERP_BILINEAR);
+ }
+ }
+ else if (self->priv->orig_pixbuf_scale > 1)
+ {
+ width = gdk_pixbuf_get_width (self->priv->orig_pixbuf) / self->priv->orig_pixbuf_scale;
+ height =gdk_pixbuf_get_height (self->priv->orig_pixbuf) / self->priv->orig_pixbuf_scale;
+ self->priv->rendered_pixbuf =
+ gdk_pixbuf_scale_simple (self->priv->orig_pixbuf,
+ width, height,
+ GDK_INTERP_BILINEAR);
}
if (!self->priv->rendered_pixbuf)
switch (self->priv->storage_type)
{
+ case GTK_IMAGE_SURFACE:
+ ensure_pixbuf_from_surface (self, context);
+ break;
+
case GTK_IMAGE_PIXBUF:
- if (self->priv->force_scale_pixbuf)
- ensure_pixbuf_at_size (self, context);
- else
- pixbuf = g_object_ref (self->priv->orig_pixbuf);
+ ensure_pixbuf_at_size (self, context);
break;
case GTK_IMAGE_STOCK:
return pixbuf;
}
+static gint
+get_scale_factor (GtkIconHelper *self,
+ GtkStyleContext *context)
+{
+ GdkScreen *screen;
+
+ if (self->priv->window)
+ return gdk_window_get_scale_factor (self->priv->window);
+
+ screen = gtk_style_context_get_screen (context);
+
+ /* else fall back to something that is more likely to be right than
+ * just returning 1:
+ */
+ return gdk_screen_get_monitor_scale_factor (screen, 0);
+}
+
+static gboolean
+check_invalidate_surface (GtkIconHelper *self,
+ GtkStyleContext *context)
+{
+ GtkStateFlags state;
+ int scale;
+
+ state = gtk_style_context_get_state (context);
+ scale = get_scale_factor (self, context);
+
+ if ((self->priv->rendered_surface != NULL) &&
+ (self->priv->last_surface_state == state) &&
+ (self->priv->last_surface_scale == scale))
+ return FALSE;
+
+ self->priv->last_surface_state = state;
+ self->priv->last_surface_scale = scale;
+
+ if (self->priv->rendered_surface)
+ cairo_surface_destroy (self->priv->rendered_surface);
+ self->priv->rendered_surface = NULL;
+
+ return TRUE;
+}
+
+static void
+ensure_surface_from_surface (GtkIconHelper *self,
+ GtkStyleContext *context)
+{
+ if (!check_invalidate_surface (self, context))
+ return;
+
+ if (self->priv->rendered_surface)
+ return;
+
+ self->priv->rendered_surface =
+ cairo_surface_reference (self->priv->orig_surface);
+
+ get_surface_size (self, context, self->priv->orig_surface,
+ &self->priv->rendered_surface_width,
+ &self->priv->rendered_surface_height);
+}
+
+static void
+ensure_surface_from_pixbuf (GtkIconHelper *self,
+ GtkStyleContext *context)
+{
+ gint width, height;
+ GdkPixbuf *pixbuf;
+ int scale;
+
+ if (!check_invalidate_surface (self, context))
+ return;
+
+ if (self->priv->rendered_surface)
+ return;
+
+ scale = get_scale_factor (self, context);
+
+ if (self->priv->force_scale_pixbuf &&
+ (self->priv->pixel_size != -1 ||
+ self->priv->icon_size != GTK_ICON_SIZE_INVALID))
+ {
+ ensure_icon_size (self, context, &width, &height);
+
+ if (scale != self->priv->orig_pixbuf_scale ||
+ width < gdk_pixbuf_get_width (self->priv->orig_pixbuf) / self->priv->orig_pixbuf_scale ||
+ height < gdk_pixbuf_get_height (self->priv->orig_pixbuf) / self->priv->orig_pixbuf_scale)
+ {
+ width = MIN (width * scale, gdk_pixbuf_get_width (self->priv->orig_pixbuf) * scale / self->priv->orig_pixbuf_scale);
+ height = MIN (height * scale, gdk_pixbuf_get_height (self->priv->orig_pixbuf) * scale / self->priv->orig_pixbuf_scale);
+
+ pixbuf = gdk_pixbuf_scale_simple (self->priv->orig_pixbuf,
+ width, height,
+ GDK_INTERP_BILINEAR);
+ }
+ else
+ {
+ pixbuf = g_object_ref (self->priv->orig_pixbuf);
+ scale = self->priv->orig_pixbuf_scale;
+ }
+ }
+ else
+ {
+ pixbuf = g_object_ref (self->priv->orig_pixbuf);
+ scale = self->priv->orig_pixbuf_scale;
+ }
+
+ self->priv->rendered_surface_width = (gdk_pixbuf_get_width (pixbuf) + scale - 1) / scale;
+ self->priv->rendered_surface_height = (gdk_pixbuf_get_height (pixbuf) + scale - 1) / scale;
+
+ self->priv->rendered_surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale, self->priv->window);
+ g_object_unref (pixbuf);
+}
+
+static void
+ensure_surface_for_icon_set (GtkIconHelper *self,
+ GtkStyleContext *context,
+ GtkIconSet *icon_set)
+{
+ gint scale;
+
+ if (!check_invalidate_surface (self, context))
+ return;
+
+ scale = get_scale_factor (self, context);
+
+ self->priv->rendered_surface =
+ gtk_icon_set_render_icon_surface (icon_set, context,
+ self->priv->icon_size,
+ scale, self->priv->window);
+
+ if (self->priv->rendered_surface)
+ get_surface_size (self, context, self->priv->rendered_surface,
+ &self->priv->rendered_surface_width,
+ &self->priv->rendered_surface_height);
+}
+
+static void
+ensure_stated_surface_from_info (GtkIconHelper *self,
+ GtkStyleContext *context,
+ GtkIconInfo *info,
+ int scale)
+{
+ GdkPixbuf *destination = NULL;
+ cairo_surface_t *surface;
+ gboolean symbolic;
+
+ symbolic = FALSE;
+
+ if (info)
+ destination =
+ gtk_icon_info_load_symbolic_for_context (info,
+ context,
+ &symbolic,
+ NULL);
+
+ if (destination == NULL)
+ {
+ GtkIconSet *icon_set;
+ icon_set = gtk_style_context_lookup_icon_set (context, GTK_STOCK_MISSING_IMAGE);
+
+ destination =
+ gtk_icon_set_render_icon_pixbuf (icon_set, context, self->priv->icon_size);
+ }
+ else if (!symbolic)
+ {
+ GtkIconSource *source;
+ GdkPixbuf *rendered;
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+
+ source = gtk_icon_source_new ();
+ gtk_icon_source_set_pixbuf (source, destination);
+ /* The size here is arbitrary; since size isn't
+ * wildcarded in the source, it isn't supposed to be
+ * scaled by the engine function
+ */
+ gtk_icon_source_set_size (source,
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gtk_icon_source_set_size_wildcarded (source, FALSE);
+
+ rendered = gtk_render_icon_pixbuf (context, source, (GtkIconSize) -1);
+ gtk_icon_source_free (source);
+
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+
+ g_object_unref (destination);
+ destination = rendered;
+ }
+
+ surface = NULL;
+ if (destination)
+ {
+ surface = gdk_cairo_surface_create_from_pixbuf (destination, scale, self->priv->window);
+
+ self->priv->rendered_surface_width =
+ (gdk_pixbuf_get_width (destination) + scale - 1) / scale;
+ self->priv->rendered_surface_height =
+ (gdk_pixbuf_get_height (destination) + scale - 1) / scale;
+ }
+
+ self->priv->rendered_surface = surface;
+}
+
+static void
+ensure_surface_for_icon_name_or_gicon (GtkIconHelper *self,
+ GtkStyleContext *context)
+{
+ GtkIconTheme *icon_theme;
+ gint width, height, scale;
+ GtkIconInfo *info;
+ GtkIconLookupFlags flags;
+
+ if (!check_invalidate_surface (self, context))
+ return;
+
+ icon_theme = gtk_icon_theme_get_default ();
+ flags = get_icon_lookup_flags (self);
+
+ ensure_icon_size (self, context, &width, &height);
+ scale = get_scale_factor (self, context);
+
+ if (self->priv->storage_type == GTK_IMAGE_ICON_NAME &&
+ self->priv->icon_name != NULL)
+ {
+ info = gtk_icon_theme_lookup_icon_for_scale (icon_theme,
+ self->priv->icon_name,
+ MIN (width, height),
+ scale, flags);
+ }
+ else if (self->priv->storage_type == GTK_IMAGE_GICON &&
+ self->priv->gicon != NULL)
+ {
+ info = gtk_icon_theme_lookup_by_gicon_for_scale (icon_theme,
+ self->priv->gicon,
+ MIN (width, height),
+ scale, flags);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ return;
+ }
+
+ ensure_stated_surface_from_info (self, context, info, scale);
+
+ if (info)
+ g_object_unref (info);
+}
+
+cairo_surface_t *
+_gtk_icon_helper_ensure_surface (GtkIconHelper *self,
+ GtkStyleContext *context)
+{
+ cairo_surface_t *surface = NULL;
+ GtkIconSet *icon_set;
+
+ switch (self->priv->storage_type)
+ {
+ case GTK_IMAGE_SURFACE:
+ ensure_surface_from_surface (self, context);
+ break;
+
+ case GTK_IMAGE_PIXBUF:
+ ensure_surface_from_pixbuf (self, context);
+ break;
+
+ case GTK_IMAGE_STOCK:
+ icon_set = gtk_style_context_lookup_icon_set (context, self->priv->stock_id);
+ if (icon_set != NULL)
+ ensure_surface_for_icon_set (self, context, icon_set);
+ else
+ surface = NULL;
+ break;
+
+ case GTK_IMAGE_ICON_SET:
+ icon_set = self->priv->icon_set;
+ ensure_surface_for_icon_set (self, context, icon_set);
+ break;
+
+ case GTK_IMAGE_ICON_NAME:
+ case GTK_IMAGE_GICON:
+ ensure_surface_for_icon_name_or_gicon (self, context);
+ break;
+
+ case GTK_IMAGE_ANIMATION:
+ case GTK_IMAGE_EMPTY:
+ default:
+ surface = NULL;
+ break;
+ }
+
+ if (surface == NULL &&
+ self->priv->rendered_surface != NULL)
+ surface = cairo_surface_reference (self->priv->rendered_surface);
+
+ return surface;
+}
+
void
_gtk_icon_helper_get_size (GtkIconHelper *self,
GtkStyleContext *context,
gint *width_out,
gint *height_out)
{
- GdkPixbuf *pix;
+ cairo_surface_t *surface;
gint width, height;
width = height = 0;
- pix = _gtk_icon_helper_ensure_pixbuf (self, context);
+ surface = _gtk_icon_helper_ensure_surface (self, context);
- if (pix != NULL)
+ if (surface != NULL)
{
- width = gdk_pixbuf_get_width (pix);
- height = gdk_pixbuf_get_height (pix);
-
- g_object_unref (pix);
+ width = self->priv->rendered_surface_width;
+ height = self->priv->rendered_surface_height;
+ cairo_surface_destroy (surface);
}
else if (self->priv->storage_type == GTK_IMAGE_ANIMATION)
{
}
}
+void
+_gtk_icon_helper_set_surface (GtkIconHelper *self,
+ cairo_surface_t *surface)
+{
+ _gtk_icon_helper_clear (self);
+
+ if (surface != NULL)
+ {
+ self->priv->storage_type = GTK_IMAGE_SURFACE;
+ self->priv->orig_surface = cairo_surface_reference (surface);
+ }
+}
+
void
_gtk_icon_helper_set_stock_id (GtkIconHelper *self,
const gchar *stock_id,
return self->priv->icon_set;
}
+cairo_surface_t *
+_gtk_icon_helper_peek_surface (GtkIconHelper *self)
+{
+ return self->priv->orig_surface;
+}
+
const gchar *
_gtk_icon_helper_get_stock_id (GtkIconHelper *self)
{
gdouble x,
gdouble y)
{
- GdkPixbuf *pixbuf;
+ cairo_surface_t *surface;
- pixbuf = _gtk_icon_helper_ensure_pixbuf (self, context);
-
- if (pixbuf != NULL)
+ surface = _gtk_icon_helper_ensure_surface (self, context);
+ if (surface != NULL)
{
- gtk_render_icon (context, cr, pixbuf, x, y);
- g_object_unref (pixbuf);
+ gtk_render_icon_surface (context, cr, surface, x, y);
+ cairo_surface_destroy (surface);
}
}
_gtk_icon_helper_invalidate (self);
}
}
+
+void
+_gtk_icon_helper_set_pixbuf_scale (GtkIconHelper *self,
+ int scale)
+{
+ if (self->priv->orig_pixbuf_scale != scale)
+ {
+ self->priv->orig_pixbuf_scale = scale;
+ _gtk_icon_helper_invalidate (self);
+ }
+}
+
+int
+_gtk_icon_helper_get_pixbuf_scale (GtkIconHelper *self)
+{
+ return self->priv->orig_pixbuf_scale;
+}